#!/usr/bin/env python
#  -*- coding: utf-8 -*- 
__author__ = 'liu'

"""
无疆策略
修改记录:
1. 2024.10.27 基于川流策略创建，修改点：
    (1) 去掉ATR止盈
    (2) 改为AF跟踪出场
2. 2024.11.30 修改点：
    (1) 改回吊灯出场
    (2) 基准止损改为百分比率
3. 2024.12.1 修改点：
    (1) 梳理了多空门槛、开仓上下限的关系
4. 2025.3.7 修改点：
    (1) 改用v16库
    (2) 允许使用主连合约映射
5. 2025.6.30 修改点：
    (1) 修复更新参数会覆盖主力映射合约的问题
"""
import datetime
from time import sleep
from sys import argv, exit
from os.path import exists
from pathlib import Path
from logging import getLogger
from numpy import isnan
from account_setting import *
from common_lib_v16 import *
from std_margin_ratio import get_std_margin_ratio
from tqsdk.tafunc import hhv, llv
from tqsdk import TqApi, TqAuth, TqAccount, TargetPosTask, TqKq, TqSim, TqBacktest

# 记录日志并打印
def log_and_print(str):
    logger.info(str)

# 记录日志、打印、邮件
def log_print_mail(str):
    logger.info(str)
    # 实盘才发邮件
    if run_mode > 2:
        str_list = str.split(' ')
        mail_header = '【' + CN_NAME + '】' + str_list[2] + str_list[3]
        send_email(mail_addr, auth_code, mail_to, mail_header, str)

# 计算手数
def calc_lots(quote, balance, order_size):
    if order_size < 1.0:
        last_price = quote.pre_close if isnan(quote.last_price) else quote.last_price
        lots = int(balance * order_size / (margin_ratio * last_price * quote.volume_multiple))
    else:
        lots = int(order_size)
    return lots

# 资金占用检查
def available_check(quote, available, balance):
    global LOTS, timing_start_flag
    if (isnan(available) or isnan(balance)):
        log_and_print(f"{quote.datetime} 【资金检查】合约: {SYMBOL} 数据异常")
        return False
    else:
        if LOTS < 1:
            log_and_print(f"{quote.datetime} 【资金检查】合约: {SYMBOL} 下单手数异常")
            return False
        elif ((available - margin_ratio * quote.last_price * quote.volume_multiple * LOTS) >= balance * (1 - capital_ratio)):
            return True
        else:
            log_and_print(f"{quote.datetime} 【资金检查】合约: {SYMBOL} 资金不足, 可用资金: {available:.2f}, 总权益: {balance:.2f}")
            LOTS = int((available - balance * (1 - capital_ratio)) / (margin_ratio * quote.last_price * quote.volume_multiple))
            save_variable(JSON_NAME, "LOTS", LOTS)
            if LOTS > 0:                
                log_and_print(f"{quote.datetime} 【资金检查】合约: {SYMBOL} 限制仓位后下单手数减少, 新的手数: {LOTS}")
                return True
            else:
                log_and_print(f"{quote.datetime} 【资金检查】合约: {SYMBOL} 限制仓位后剩余资金不足以开仓1手")
                # 对于非独立信号，比如通道突破类，资金不足直接停止择时，避免后续资金足够了再开仓成本抬高
                timing_start_flag = -1  # 停止择时
                save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                return False

# 价格有效检查
def price_valid_check(quote, ticks):
    # 判断空值
    if (isnan(quote.ask_price1) or isnan(quote.bid_price1)):
        log_and_print(f"{quote.datetime} 【价格检查】合约: {SYMBOL} 买卖1档价格异常")
        return False
    # 回测模式（涨跌停价无数据）
    elif (isnan(quote.upper_limit) or isnan(quote.lower_limit)):
        return True
    # 实盘模式
    else:
        # 买卖挂单在涨跌停价ticks跳外则合法
        if ((quote.ask_price1 < quote.upper_limit - ticks * quote.price_tick) and 
            (quote.bid_price1 > quote.lower_limit + ticks * quote.price_tick)):
            return True
        else:
            log_and_print(f"{quote.datetime} 【价格检查】合约: {SYMBOL} 价格在涨跌停{ticks}跳内")
            return False

# 获取超价下单价格
def get_price(direction):
    if direction == "BUY":
        price = quote.ask_price1
        if price != price:
            price = quote.bid_price1
        if price != price:
            price = quote.last_price
        limit_price = price + 3 * quote.price_tick
    else:
        price = quote.bid_price1
        if price != price:
            price = quote.ask_price1
        if price != price:
            price = quote.last_price
        limit_price = price - 3 * quote.price_tick

    return limit_price

# 获取配置参数
def get_config_params(strategy_name, index, update = False):
    global SYMBOL, PERIOD, order_size, \
            long_threshold, long_stop_price, long_profit_price_1, long_profit_price_2, \
            short_threshold, short_stop_price, short_profit_price_1, short_profit_price_2, \
            profit_price_decline, profit_decline_after_bars, profit_exit_mode, entry_n, \
            base_stop_ratio, step_coefficient, final_coefficient, permitted_dir, margin_ratio_add, capital_ratio, run_mode
    filename = "config_" + strategy_name + ".ini"
    ser = get_config(filename, index)
    print(ser)
    if (not ser.empty):
        # 合约代码、周期、手数/仓位
        if update == False:
            SYMBOL = ser.iloc[0].split(':')[1] + '.' + ser.iloc[1]    # 合约代码
            PERIOD = int(ser.iloc[2])                                 # 策略周期（秒）
        order_size = float(ser.iloc[3])                           # 下单手数/仓位
        # 策略参数
        long_threshold = float(ser.iloc[4])                       # 做多启动门槛（-1失效）
        long_stop_price = float(ser.iloc[5])                      # 做多止损价位（-1失效）
        long_profit_price_1 = float(ser.iloc[6])                  # 做多止盈价位1（-1失效）
        long_profit_price_2 = float(ser.iloc[7])                  # 做多止盈价位2（-1失效）
        short_threshold = float(ser.iloc[8])                      # 做空启动门槛（-1失效）
        short_stop_price = float(ser.iloc[9])                     # 做空止损价位（-1失效）
        short_profit_price_1 = float(ser.iloc[10])                # 做空止盈价位1（-1失效）
        short_profit_price_2 = float(ser.iloc[11])                # 做空止盈价位2（-1失效）
        profit_price_decline = float(ser.iloc[12])                # 每bar止盈减少跳数
        profit_decline_after_bars = int(ser.iloc[13])             # 止盈减少起始bar数
        profit_exit_mode = int(ser.iloc[14])                      # 止盈出场模式（0:全部 1:分批）
        entry_n = int(ser.iloc[15])                               # 唐奇安通道入场N（0失效）
        base_stop_ratio = float(ser.iloc[16])                     # 基准止损比率
        step_coefficient = float(ser.iloc[17])                    # 倍乘系数降低步长
        final_coefficient = float(ser.iloc[18])                   # 最终倍乘系数
        permitted_dir = int(ser.iloc[19])                         # 允许下单方向（0:双向 1:多 2:空）
        margin_ratio_add = float(ser.iloc[20])                    # 保证金比率加成（基于交易所标准保证金比率）
        capital_ratio = float(ser.iloc[21])                       # 资金占比上限
        run_mode = int(ser.iloc[22])                              # 运行模式（0:回测 1:模拟A 2:模拟B 3:实盘A 4:实盘B）

# 常量
out_ticks = 30                                                    # 距离涨跌停跳数

# 中间变量
LOTS = 0                                                          # 下单手数
long_local_pos = 0                                                # 多头本地持仓
short_local_pos = 0                                               # 空头本地持仓
long_local_profit_price_1 = -1.0                                  # 多头本地止盈价1
long_local_profit_price_2 = -1.0                                  # 多头本地止盈价2
short_local_profit_price_1 = -1.0                                 # 空头本地止盈价1
short_local_profit_price_2 = -1.0                                 # 空头本地止盈价2
long_open_limit = -1.0                                            # 多头开仓门槛
short_open_limit = -1.0                                           # 空头开仓门槛
long_stop_limit = -1.0                                            # 多头止损价
short_stop_limit = -1.0                                           # 空头止损价
max_high_after_entry = -1.0                                       # 最大的最高价
min_low_after_entry = -1.0                                        # 最小的最低价
base_stop = -1.0                                                  # 基准止损
coefficient = 1.0                                                 # 止损倍乘系数
position_hold_bars = 0                                            # 持仓Bar数
decline_cofficient = 1.0                                          # 止盈降低一二级比例
timing_start_flag = 0                                             # 择时启动标记（0:初始 1:多 2:空 -1:主动停止）
execute_bypass = False                                            # 下单执行旁路标记

# 解析命令行入参
try:
    if len(argv) == 2:
        web_port = argv[1]                                        # 访问端口
        run_index = int(web_port) % 100                           # 运行索引
    else:
        print("输入参数数量出错！")
        exit()
except Exception as InputError:
    print(InputError)
    exit()

# 策略名
CN_NAME = "无疆"                                                  # 中文名
EN_NAME = "wujiang"                                               # 英文名

# 监控网址
WEB_GUI = "http://127.0.0.1:" + web_port                          # 本地地址

# 获取对应运行索引的配置参数
get_config_params(EN_NAME, run_index)

re_connect = True
while re_connect:
    # API实例、JSON文件名
    try:
        if run_mode == 1:  # 模拟A
            api = TqApi(TqKq(), auth=TqAuth(USER_SIM, PWD_SIM), web_gui=WEB_GUI)
            file_name = "sim_" + USER_SIM + "_" + EN_NAME + "_" + SYMBOL
        elif run_mode == 2:  # 模拟B
            api = TqApi(TqKq(), auth=TqAuth(USER_SIM_B, PWD_SIM_B), web_gui=WEB_GUI)
            file_name = "sim_" + USER_SIM_B + "_" + EN_NAME + "_" + SYMBOL
        elif run_mode == 3:  # 实盘A
            api = TqApi(TqAccount(BROKER_ID_A, ACCOUNT_ID_A, PASSWORD_A), auth=TqAuth(USER_RUN_A, PWD_RUN_A), web_gui=WEB_GUI)
            file_name = "run_" + ACCOUNT_ID_A + "_" + EN_NAME + "_"  + SYMBOL
        elif run_mode == 4:  # 实盘B
            api = TqApi(TqAccount(BROKER_ID_B, ACCOUNT_ID_B, PASSWORD_B), auth=TqAuth(USER_RUN_B, PWD_RUN_B), web_gui=WEB_GUI)
            file_name = "run_" + ACCOUNT_ID_B + "_" + EN_NAME + "_"  + SYMBOL
        else:  # 回测
            api = TqApi(TqSim(init_balance=INIT_BALANCE), backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE), \
                        auth=TqAuth(USER_BACKTEST, PWD_BACKTEST), web_gui=WEB_GUI)
            file_name = "backtest_" + USER_BACKTEST + "_" + EN_NAME + "_"  + SYMBOL
        print(api)
    except Exception as LoginError:
        print(LoginError)
        re_connect = True
        print(f"【告警】策略: {CN_NAME} 合约: {SYMBOL} 账号连接失败重连中...")
        sleep(5)
    else:
        re_connect = False

# 引用
if contains_digit(SYMBOL):
    quote = api.get_quote(SYMBOL)
else:
    # 主连映射标的合约
    quote_main = api.get_quote('KQ.m@' + SYMBOL)
    SYMBOL = quote_main.underlying_symbol
    quote = api.get_quote(SYMBOL)
klines = api.get_kline_serial(SYMBOL, PERIOD)
tsa  = api.get_tick_serial(SYMBOL)
position = api.get_position(SYMBOL)
account = api.get_account()

# 仓位相关
if run_mode > 2:
    target_pos = TargetPosTask(api, SYMBOL, price = get_price)
else:
    target_pos = TargetPosTask(api, SYMBOL)  # 回测、模拟不用超价
long_local_pos = position.pos_long
short_local_pos = position.pos_short

# 保证金比率等于标准比率加期货公司比率加成
margin_ratio = get_std_margin_ratio(SYMBOL) + margin_ratio_add

# 日志配置
logger = getLogger()
log_file = get_log_file_path(file_name)
set_log_rule(logger, log_file)

# 邮件配置
MAIL_AUTH_FILE = "./mail.json"
mail_addr = get_variable(MAIL_AUTH_FILE, "mail_addr", "")
auth_code = get_variable(MAIL_AUTH_FILE, "auth_code", "")
mail_to = get_variable(MAIL_AUTH_FILE, "mail_to", "")

# JSON文件
JSON_NAME = get_json_file_path(file_name)

# 开市前获取保存的本地数据
long_stop_limit = get_variable(JSON_NAME, "long_stop_limit", -1.0)
short_stop_limit = get_variable(JSON_NAME, "short_stop_limit", -1.0)
max_high_after_entry = get_variable(JSON_NAME, "max_high_after_entry", -1.0)
min_low_after_entry = get_variable(JSON_NAME, "min_low_after_entry", -1.0)
base_stop = get_variable(JSON_NAME, "base_stop", -1.0)
coefficient = get_variable(JSON_NAME, "coefficient", 1.0)
position_hold_bars = get_variable(JSON_NAME, "position_hold_bars", 0)
timing_start_flag = get_variable(JSON_NAME, "timing_start_flag", 0)
actual_pos = get_variable(JSON_NAME, "LOTS", 0)        # 实际下单手数
if (long_local_pos == 0 and short_local_pos == 0):
    LOTS = calc_lots(quote, account.balance, order_size)
    save_variable(JSON_NAME, "LOTS", LOTS)
else:
    # json意外被删，获取不到pos，只能以当次持仓手数作为全量手数
    if actual_pos <= 0:
        LOTS = max(long_local_pos, short_local_pos)
        save_variable(JSON_NAME, "LOTS", LOTS)
    else:
        LOTS = actual_pos
        # 此处不需要存LOTS
if LOTS < 1:
    log_and_print(f"【初始|策略退出】策略: {CN_NAME} 合约: {SYMBOL} 周期: {PERIOD}秒 手数低于1手")
    exit()
log_and_print(f"【初始|策略运行】策略: {CN_NAME} 合约: {SYMBOL} 周期: {PERIOD}秒 手数: {LOTS} 保证金比率: {margin_ratio:.3f}")

while True:
    if entry_n > 0:
        # 高低点计算
        entry_high = hhv(klines.high, entry_n)
        entry_low = llv(klines.low, entry_n)
        # 多空开仓门槛
        long_open_limit = entry_high.iloc[-2]
        short_open_limit = entry_low.iloc[-2]

        # 画图
        klines["HH_MAIN"] = entry_high
        klines["HH_MAIN.color"] = "red"
        klines["LL_MAIN"] = entry_low
        klines["LL_MAIN.color"] = "black"
    else:
        long_open_limit = -1.0
        short_open_limit = -1.0

    api.wait_update()

    # 暂停、退出、复位策略
    pause_path = Path("./pause_" + EN_NAME + "_" + str(run_index))
    exit_path = Path("./exit_" + EN_NAME + "_" + str(run_index))
    if exists(exit_path):
        api.close()
        break
    elif exists(pause_path):
        execute_bypass = True
    else:
        if execute_bypass == True:
            execute_bypass = False
            # 更新配置参数
            get_config_params(EN_NAME, run_index, True)

            # 保证金比率等于标准比率加期货公司比率加成
            margin_ratio = get_std_margin_ratio(SYMBOL) + margin_ratio_add
            if (long_local_pos == 0 and short_local_pos == 0):
                LOTS = calc_lots(quote, account.balance, order_size)
                save_variable(JSON_NAME, "LOTS", LOTS)
                if LOTS < 1:
                    log_and_print(f"【更新|策略退出】策略: {CN_NAME} 合约: {SYMBOL} 周期: {PERIOD}秒 手数低于1手")
                    exit()
                log_and_print(f"【更新|策略运行】策略: {CN_NAME} 合约: {SYMBOL} 周期: {PERIOD}秒 手数: {LOTS} 保证金比率: {margin_ratio:.3f}")

    # K线变化
    if api.is_changing(klines.iloc[-1], ["datetime", "open"]):
        if entry_n > 0:
            # 多空开仓门槛
            long_open_limit = entry_high.iloc[-1]
            short_open_limit = entry_low.iloc[-1]

            if ((position.pos_long == 0) and (position.pos_long == long_local_pos) and
                (position.pos_short == 0) and (position.pos_short == short_local_pos)):
                log_and_print(f"{quote.datetime} 【参考数值】合约: {SYMBOL} 多方门槛: {long_open_limit:.2f} 空方门槛: {short_open_limit:.2f}")
        else:
            long_open_limit = -1.0
            short_open_limit = -1.0

        # 计算止损
        if ((position.pos_long > 0) and (position.pos_long == long_local_pos)):
            # 计算最大的最高价
            if position_hold_bars == 0:
                max_high_after_entry = klines.high.iloc[-2]
            else:
                max_high_after_entry = max(max_high_after_entry, klines.high.iloc[-2])
            save_variable(JSON_NAME, "max_high_after_entry", max_high_after_entry)
            # 经过计算，这根吊灯出场线会随着持仓时间的增加变的越来越敏感
            long_stop_limit = max(long_stop_limit, (max_high_after_entry - coefficient * base_stop))
            if (long_stop_price > 0.0 and long_stop_limit < long_stop_price):
                long_stop_limit = long_stop_price
            save_variable(JSON_NAME, "long_stop_limit", long_stop_limit)
            log_and_print(f"{quote.datetime} 【买方|更新止损】合约: {SYMBOL} 止损价: {long_stop_limit:.2f} 止损系数: {coefficient:.2f}")
            # 持仓计数
            position_hold_bars += 1
            save_variable(JSON_NAME, "position_hold_bars", position_hold_bars)
            coefficient -= step_coefficient
            coefficient = max(coefficient, final_coefficient)
            save_variable(JSON_NAME, "coefficient", coefficient)
        elif ((position.pos_short > 0) and (position.pos_short == short_local_pos)):
            # 计算最小的最低价
            if position_hold_bars == 0:
                min_low_after_entry = klines.low.iloc[-2]
            else:
                min_low_after_entry = min(min_low_after_entry, klines.low.iloc[-2])
            save_variable(JSON_NAME, "min_low_after_entry", min_low_after_entry)
            # 经过计算，这根吊灯出场线会随着持仓时间的增加变的越来越敏感
            short_stop_limit = min(short_stop_limit, (min_low_after_entry + coefficient * base_stop))
            if (short_stop_price > 0.0 and short_stop_limit > short_stop_price):
                short_stop_limit = short_stop_price
            save_variable(JSON_NAME, "short_stop_limit", short_stop_limit)
            log_and_print(f"{quote.datetime} 【卖方|更新止损】合约: {SYMBOL} 止损价: {short_stop_limit:.2f} 止损系数: {coefficient:.2f}")
            # 持仓计数
            position_hold_bars += 1
            save_variable(JSON_NAME, "position_hold_bars", position_hold_bars)
            coefficient -= step_coefficient
            coefficient = max(coefficient, final_coefficient)
            save_variable(JSON_NAME, "coefficient", coefficient)

    # 行情变化
    if api.is_changing(quote, "datetime"):
        # 过滤非交易时段
        now = datetime.datetime.strptime(quote.datetime, "%Y-%m-%d %H:%M:%S.%f")
        not_trading_time = ((now.hour > 3 and now.hour < 9) or (now.hour > 15 and now.hour < 21))

        # 设置择时标记
        if (timing_start_flag != -1):
            if (long_threshold > 0.0 and quote.last_price > long_threshold):
                timing_start_flag = 1  # 启动择时做多
            elif (short_threshold > 0.0 and quote.last_price < short_threshold):
                timing_start_flag = 2  # 启动择时做空
            else:
                timing_start_flag = 0  # 择时标记复位

        # 暂停旁路或不在交易时间
        if ((execute_bypass == True) or (not_trading_time)):
            continue

        # 开平仓
        if ((position.pos_long == 0) and (position.pos_long == long_local_pos) and
            (position.pos_short == 0) and (position.pos_short == short_local_pos)):
            if (((long_open_limit > 0.0 and quote.last_price > long_open_limit and (timing_start_flag == 1 or (long_threshold < 0.0 and short_threshold < 0.0))) or
                 (long_open_limit < 0.0 and timing_start_flag == 1)) and
                (permitted_dir == 0 or permitted_dir == 1) and
                (price_valid_check(quote, out_ticks) == True) and
                (available_check(quote, account.available, account.balance) == True)):
                LOTS = calc_lots(quote, account.balance, order_size)
                save_variable(JSON_NAME, "LOTS", LOTS)
                long_local_pos = LOTS
                position_hold_bars = 0
                save_variable(JSON_NAME, "position_hold_bars", position_hold_bars)
                coefficient = 1.0
                save_variable(JSON_NAME, "coefficient", coefficient)
                if long_stop_price > 0.0:
                    base_stop = min(base_stop_ratio * quote.last_price, quote.ask_price1 - long_stop_price)
                else:
                    base_stop = base_stop_ratio * quote.last_price
                save_variable(JSON_NAME, "base_stop", base_stop)
                long_stop_limit = quote.ask_price1 - base_stop
                save_variable(JSON_NAME, "long_stop_limit", long_stop_limit)
                long_local_profit_price_1 = long_profit_price_1
                long_local_profit_price_2 = long_profit_price_2
                target_pos.set_target_volume(LOTS)
                log_print_mail(f"{quote.datetime} 【买开】合约: {SYMBOL} 手数: {LOTS} 价格: {quote.ask_price1:.2f} 止损价: {long_stop_limit:.2f} 止盈价I/II级: {long_local_profit_price_1:.2f}/{long_local_profit_price_2:.2f}")
            elif (((short_open_limit > 0.0 and quote.last_price < short_open_limit and (timing_start_flag == 2 or (long_threshold < 0.0 and short_threshold < 0.0))) or
                   (short_open_limit < 0.0 and timing_start_flag == 2)) and
                  (permitted_dir == 0 or permitted_dir == 2) and
                  (price_valid_check(quote, out_ticks) == True) and
                  (available_check(quote, account.available, account.balance) == True)):
                LOTS = calc_lots(quote, account.balance, order_size)
                save_variable(JSON_NAME, "LOTS", LOTS)
                short_local_pos = LOTS
                position_hold_bars = 0
                save_variable(JSON_NAME, "position_hold_bars", position_hold_bars)
                coefficient = 1.0
                save_variable(JSON_NAME, "coefficient", coefficient)
                if short_stop_price > 0.0:
                    base_stop = min(base_stop_ratio * quote.last_price, short_stop_price - quote.bid_price1)
                else:
                    base_stop = base_stop_ratio * quote.last_price
                save_variable(JSON_NAME, "base_stop", base_stop)
                short_stop_limit = quote.bid_price1 + base_stop
                save_variable(JSON_NAME, "short_stop_limit", short_stop_limit)
                short_local_profit_price_1 = short_profit_price_1
                short_local_profit_price_2 = short_profit_price_2
                target_pos.set_target_volume(-LOTS)
                log_print_mail(f"{quote.datetime} 【卖开】合约: {SYMBOL} 手数: {LOTS} 价格: {quote.bid_price1:.2f} 止损价: {short_stop_limit:.2f} 止盈价I/II级: {short_local_profit_price_1:.2f}/{short_local_profit_price_2:.2f}")
        elif ((position.pos_long > 0) and (position.pos_long == long_local_pos)):
            if (position_hold_bars >= profit_decline_after_bars and profit_price_decline > 0.0):
                if long_profit_price_1 > 0:
                    local_profit_price = max(long_profit_price_1 - (position_hold_bars - profit_decline_after_bars + 1) * profit_price_decline,
                                             (long_profit_price_1 + long_threshold) / 2)
                    if ((long_local_profit_price_1 > 0.0 and long_local_profit_price_1 > local_profit_price) or (long_local_profit_price_1 < 0.0)):
                        long_local_profit_price_1 = local_profit_price                    
                        log_and_print(f"{quote.datetime} 【多方|止盈1更新】合约: {SYMBOL} 价格: {long_local_profit_price_1:.2f}")
                else:
                    long_local_profit_price_1 = -1.0
                if (long_profit_price_2 > 0.0):
                    if long_profit_price_1 > 0.0:
                        decline_cofficient = (long_profit_price_2 - long_threshold) / (long_profit_price_1 - long_threshold)
                    else:
                        decline_cofficient = 1.0
                    local_profit_price = max(long_profit_price_2 - (position_hold_bars - profit_decline_after_bars + 1) * profit_price_decline * decline_cofficient,
                                             (long_profit_price_2 + long_threshold) / 2)
                    if ((long_local_profit_price_2 > 0.0 and long_local_profit_price_2 > local_profit_price) or (long_local_profit_price_2 < 0.0)):
                        long_local_profit_price_2 = local_profit_price
                        log_and_print(f"{quote.datetime} 【多方|止盈2更新】合约: {SYMBOL} 价格: {long_local_profit_price_2:.2f}")
                else:
                    long_local_profit_price_2 = -1.0
            else:
                long_local_profit_price_1 = long_profit_price_1
                long_local_profit_price_2 = long_profit_price_2

            if (long_stop_limit > 0.0 and quote.last_price < long_stop_limit):
                if ((timing_start_flag != -1) and (entry_n <= 0)):
                    timing_start_flag = -1  # 停止择时
                    save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                    log_and_print(f"{quote.datetime} 【买方|止损停止择时】合约: {SYMBOL}")
                long_local_pos = 0
                target_pos.set_target_volume(0)
                log_print_mail(f"{quote.datetime} 【卖平|止损】合约: {SYMBOL} 手数: {position.pos_long} 价格: {quote.bid_price1:.2f}")
            elif (long_local_profit_price_2 > 0.0 and quote.last_price >= long_local_profit_price_2 and profit_exit_mode == 1):
                if timing_start_flag != -1:
                    timing_start_flag = -1  # 停止择时
                    save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                    log_and_print(f"{quote.datetime} 【买方|止盈停止择时】合约: {SYMBOL}")
                long_local_pos = 0
                target_pos.set_target_volume(0)
                log_print_mail(f"{quote.datetime} 【卖平|止盈】合约: {SYMBOL} 手数: {position.pos_long} 价格: {quote.bid_price1:.2f}")
            elif (long_local_profit_price_1 > 0.0 and quote.last_price >= long_local_profit_price_1):
                if timing_start_flag != -1:
                    timing_start_flag = -1  # 停止择时
                    save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                    log_and_print(f"{quote.datetime} 【买方|止盈停止择时】合约: {SYMBOL}")
                # 全部出场
                if profit_exit_mode == 0:
                    long_local_pos = 0
                    target_pos.set_target_volume(0)
                    log_print_mail(f"{quote.datetime} 【卖平|止盈】合约: {SYMBOL} 手数: {position.pos_long} 价格: {quote.bid_price1:.2f}")
                # 分批出场
                elif profit_exit_mode == 1:
                    if long_local_pos == LOTS:
                        long_local_pos = LOTS // 2
                        target_pos.set_target_volume(LOTS // 2)
                        log_print_mail(f"{quote.datetime} 【卖平|止盈|一半】合约: {SYMBOL} 手数: {position.pos_long - (LOTS // 2)} 价格: {quote.bid_price1:.2f}")
        elif ((position.pos_short > 0) and (position.pos_short == short_local_pos)):
            if (position_hold_bars >= profit_decline_after_bars and profit_price_decline > 0.0):
                if (short_profit_price_1 > 0.0):
                    local_profit_price = min(short_profit_price_1 + (position_hold_bars - profit_decline_after_bars + 1) * profit_price_decline, 
                                             (short_profit_price_1 + short_threshold) / 2)
                    if ((short_local_profit_price_1 > 0.0 and short_local_profit_price_1 < local_profit_price) or (short_local_profit_price_1 < 0.0)):
                        short_local_profit_price_1 = local_profit_price  
                        log_and_print(f"{quote.datetime} 【空方|止盈1更新】合约: {SYMBOL} 价格: {short_local_profit_price_1:.2f}")
                else:
                    short_local_profit_price_1 = -1.0
                if (short_profit_price_2 > 0.0):
                    if short_profit_price_1 > 0.0:
                        decline_cofficient = (short_threshold - short_profit_price_2) / (short_threshold - short_profit_price_1)
                    else:
                        decline_cofficient = 1.0
                    local_profit_price = min(short_profit_price_2 + (position_hold_bars - profit_decline_after_bars + 1) * profit_price_decline * decline_cofficient,
                                             (short_profit_price_2 + short_threshold) / 2)
                    if ((short_local_profit_price_2 > 0.0 and short_local_profit_price_2 < local_profit_price) or (short_local_profit_price_2 < 0.0)):
                        short_local_profit_price_2 = local_profit_price  
                        log_and_print(f"{quote.datetime} 【空方|止盈2更新】合约: {SYMBOL} 价格: {short_local_profit_price_2:.2f}")
                else:
                    short_local_profit_price_2 = -1.0
            else:
                short_local_profit_price_1 = short_profit_price_1
                short_local_profit_price_2 = short_profit_price_2

            if (short_stop_limit > 0.0 and quote.last_price > short_stop_limit):
                if ((timing_start_flag != -1) and (entry_n <= 0)):
                    timing_start_flag = -1  # 停止择时
                    save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                    log_and_print(f"{quote.datetime} 【买方|止损停止择时】合约: {SYMBOL}")
                short_local_pos = 0
                target_pos.set_target_volume(0)
                log_print_mail(f"{quote.datetime} 【买平|止损】合约: {SYMBOL} 手数: {position.pos_short} 价格: {quote.ask_price1:.2f}")
            elif (short_local_profit_price_2 > 0.0 and quote.last_price <= short_local_profit_price_2 and profit_exit_mode == 1):
                if timing_start_flag != -1:
                    timing_start_flag = -1  # 停止择时
                    save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                    log_and_print(f"{quote.datetime} 【卖方|止盈停止择时】合约: {SYMBOL}")
                short_local_pos = 0
                target_pos.set_target_volume(0)
                log_print_mail(f"{quote.datetime} 【买平|止盈】合约: {SYMBOL} 手数: {position.pos_short} 价格: {quote.ask_price1:.2f}")
            elif (short_local_profit_price_1 > 0.0 and quote.last_price <= short_local_profit_price_1):
                if timing_start_flag != -1:
                    timing_start_flag = -1  # 停止择时
                    save_variable(JSON_NAME, "timing_start_flag", timing_start_flag)
                    log_and_print(f"{quote.datetime} 【卖方|止盈停止择时】合约: {SYMBOL}")
                # 全部出场
                if profit_exit_mode == 0:
                    short_local_pos = 0
                    target_pos.set_target_volume(0)
                    log_print_mail(f"{quote.datetime} 【买平|止盈】合约: {SYMBOL} 手数: {position.pos_short} 价格: {quote.ask_price1:.2f}")
                # 分批出场
                elif profit_exit_mode == 1:
                    if short_local_pos == LOTS:
                        short_local_pos = LOTS // 2
                        target_pos.set_target_volume(-(LOTS // 2))
                        log_print_mail(f"{quote.datetime} 【买平|止盈|一半】合约: {SYMBOL} 手数: {position.pos_short - (LOTS // 2)} 价格: {quote.ask_price1:.2f}")
